﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.VisualStudio.Modeling;

using DslModeling = global::Microsoft.VisualStudio.Modeling;
using DslDesign = global::Microsoft.VisualStudio.Modeling.Design;
using DslDiagrams = global::Microsoft.VisualStudio.Modeling.Diagrams;
using Microsoft.VisualStudio.Modeling.Diagrams;
using CodeDomHelper.Types;
using System.Collections;
using CustomTypes.Mono.Extensions;
using Mono.Cecil;
using devtm.AutoMapper.CustomCode.TemplateAdd;

namespace devtm.AutoMapper
{

    public static class NewMapperHelper
    {



        public static bool IsBusy = false;
        private const string mapperName = "Mapper{0}{1}";

        public static void CreateMapper(MappingModel model, string source, string target, bool recursif, LinkPolicy policy, List<String> lst, CustomTypeRepository repository)
        {

            if (lst == null)
                lst = new List<string>();

            lst.Add(source + ":" + target);

            Mapper m = model.FindMapperFor(source, target);
            MappingDestination mt = null;
            MappingSource ms = null;
            TypeElement s = null, t =null;

            bool ct = false;
            bool cs = false;
            bool cm = false;

            using (Transaction trans = model.Store.TransactionManager.BeginTransaction("Add New tree map"))
            {

                if (m == null)
                {
                    cm = true;
                    m = new Mapper(model.Partition);
                    var s1 = BuildTypeExpression.Analyse(source);
                    var t1 = BuildTypeExpression.Analyse(target);
                    m.Name = s1.Name + "To" + t1.Name;
                    SetName(model, m);
                    model.Mappers.Add(m);
                }

                if (m.MappingDestination == null)
                {
                    t = GetElementTypeFor(model, target, out ct);
                    mt = new MappingDestination(model.Partition);
                    mt.Name = "Target " + m.Name;
                    mt.ByPass = true;
                    t.DMappings.Add(mt);
                    mt.Mapper = m;
                }


                if (m.MappingSource == null)
                {
                    s = GetElementTypeFor(model, source, out cs);
                    ms = new MappingSource(model.Partition);
                    ms.Name = "Source " + m.Name;
                    ms.ByPass = true;
                    s.SMappings.Add(ms);
                    ms.Mapper = m;                   
                }

                if (t != null)
                {
                    t.AssemblyQualifiedName = string.Empty;
                    t.AssemblyQualifiedName = target;
                }

                if (s != null)
                {
                    s.AssemblyQualifiedName = string.Empty;
                    s.AssemblyQualifiedName = source;
                }

                if (mt != null)
                    mt.ByPass = false;

                if (ms != null)
                    ms.ByPass = false;

                trans.Commit();
            }

            Reorder(model, m, s, t, ct, cs, cm);

            if (recursif)
            {
                FindMaps(model, m, recursif, policy, lst, repository);
                FindGeneric(model, source, target, recursif, policy, lst, repository);


                if (m.MappingSource.TypeElement != null)
                {

                    var sourceType = repository.GetType(m.MappingSource.TypeElement.AssemblyQualifiedName);
                    if (sourceType != null && sourceType.GetInterface("IEnumerable", false) != null)
                    {

                        if (m.MappingDestination.TypeElement != null )
                        {

                            var targerType = repository.GetType(m.MappingDestination.TypeElement.AssemblyQualifiedName);
                            if (targerType != null && targerType.GetInterface("IEnumerable", false) != null)
                                FindEnumerable(model, sourceType, targerType, recursif, policy, lst, repository);
                            
                        }
                    }

                }

            }
                

        }

        private static void FindEnumerable(MappingModel model, TypeDefinition typeSource, TypeDefinition typeDestination, bool recursif, LinkPolicy policy, List<string> lst, CustomTypeRepository repository)
        {

            TypeReference[] types = null;
            TypeReference[] typet = null;
            GetGenericType(typeSource, ref types, typeDestination, ref typet, repository);

            TypeReference source = types[0];
            TypeReference target = typet[0];

            CreateMapper(model, source.AssemblyQualifiedName, target.AssemblyQualifiedName, recursif, policy, lst, repository);

        }

        private static void GetGenericType(TypeReference typeSource, ref TypeReference[] types, TypeReference typeDestination, ref TypeReference[] typet, CustomTypeRepository repository)
        {

            var ts = typeSource as GenericInstanceType;
            var tt = typeDestination as GenericInstanceType;

            if (ts != null && tt != null)
            {

                types = ts.GenericArguments.ToArray();
                typet = tt.GenericArguments.ToArray();

                //if (typeSource.BaseType != null && typeDestination.BaseType != null)
                //    GetGenericType(typeSource.BaseType, ref types, typeDestination.BaseType, ref typet);

            }

        }

        private static void Reorder(MappingModel model, Mapper m, TypeElement s, TypeElement t, bool ct, bool cs, bool cm)
        {

            using (Transaction trans = model.Store.TransactionManager.BeginTransaction("reorder the diagram"))
            {

                bool test = false;

                if (cs)
                {
                    TypeElementShape shapeSource = (TypeElementShape)PresentationViewsSubject.GetPresentation(s).FirstOrDefault();
                    shapeSource.Location = new PointD(0, 0);
                    test = true;
                }


                if (cm)
                {
                    MapperShape shapeMapper = (MapperShape)PresentationViewsSubject.GetPresentation(m).FirstOrDefault();
                    shapeMapper.Location = new PointD(3, 0);
                    test = true;
                }

                if (ct)
                {
                    TypeElementShape shapeTarget = (TypeElementShape)PresentationViewsSubject.GetPresentation(t).FirstOrDefault();
                    shapeTarget.Location = new PointD(6, 0);
                    test = true;
                }

                if (test)
                    trans.Commit();
                else
                    trans.Rollback();

            }

            using (Transaction trans = model.Store.TransactionManager.BeginTransaction("reorder the diagram"))
            {

                double y = model.Mappers.Count == 1 ? 0 : FindY(model);
                bool test = false;

                if (cs)
                {
                    TypeElementShape shapeSource = (TypeElementShape)PresentationViewsSubject.GetPresentation(s).FirstOrDefault();
                    shapeSource.Location = new PointD(0, y);
                    test = true;
                }


                if (cm)
                {
                    MapperShape shapeMapper = (MapperShape)PresentationViewsSubject.GetPresentation(m).FirstOrDefault();
                    shapeMapper.Location = new PointD(3, y);
                    test = true;
                }

                if (ct)
                {
                    TypeElementShape shapeTarget = (TypeElementShape)PresentationViewsSubject.GetPresentation(t).FirstOrDefault();
                    shapeTarget.Location = new PointD(6, y);
                    test = true;
                }

                if (test)
                    trans.Commit();
                else
                    trans.Rollback();
            }
        }

        public static double FindY(MappingModel model)
        {

            double y = -1.0;

            foreach (Mapper m in model.Mappers)
            {
                var s = (MapperShape)PresentationViewsSubject.GetPresentation(m).FirstOrDefault();
                y = Math.Max(y, s.Location.Y + s.Size.Height + 0.6);
            }
           
            return y;

        }



        private static void FindGeneric(MappingModel model, string source, string target, bool recursif, LinkPolicy policy, List<String> lst, CustomTypeRepository repository)
        {
            var ss = BuildTypeExpression.Analyse(source);
            var st = BuildTypeExpression.Analyse(target);

            if (ss.IsCanGeneric && st.IsCanGeneric && ss.CountGeneric == st.Generic)

                for (int i = 0; i < ss.Generic; i++)
                {
                    
                    BuildTypeExpression t1 = ss[i];
                    BuildTypeExpression t2 = st[i];

                    Type type1 = t1.Evaluate();
                    Type type2 = t2.Evaluate();

                    bool tt1 = !type1.IsPrimitive && type1 != typeof(string) && type1 != typeof(DateTime);
                    bool tt2 = !type2.IsPrimitive && type2 != typeof(string) && type2 != typeof(DateTime);

                    if (tt1 && tt2)
                        if (!lst.Contains(t1.AssemblyQualifiedName + ":" + t2.AssemblyQualifiedName))
                            CreateMapper(model, t1.AssemblyQualifiedName, t2.AssemblyQualifiedName, recursif, policy, lst, repository);

                }

        }

        public static string[] types = new string[] 
            { typeof(string).FullName, typeof(sbyte).FullName, typeof(byte).FullName, typeof(char).FullName
              , typeof(short).FullName, typeof(ushort).FullName, typeof(int).FullName, typeof(uint).FullName
              , typeof(long).FullName, typeof(ulong).FullName , typeof(float).FullName, typeof(double).FullName
              , typeof(Int16).FullName, typeof(Int32).FullName, typeof(Int64).FullName, typeof(System.DateTime).FullName      
              , typeof(UInt16).FullName, typeof(UInt32).FullName, typeof(UInt64).FullName, typeof(decimal?).FullName
              , typeof(bool).FullName, typeof(decimal).FullName, typeof(System.TimeSpan).FullName, typeof(UInt32?).FullName
              , typeof(sbyte?).FullName, typeof(byte?).FullName, typeof(char?).FullName, typeof(bool?).FullName
              , typeof(short?).FullName, typeof(ushort?).FullName, typeof(int?).FullName, typeof(uint?).FullName
              , typeof(long?).FullName, typeof(ulong?).FullName , typeof(float?).FullName, typeof(double?).FullName
              , typeof(Int16?).FullName, typeof(Int32?).FullName, typeof(Int64?).FullName, typeof(UInt64?).FullName
              , typeof(UInt16?).FullName, typeof(System.DateTime?).FullName, typeof(System.TimeSpan?).FullName
              , typeof(System.Guid).FullName, typeof(byte[]).FullName
            };

        private static void FindMaps(MappingModel model, Mapper m, bool recursif, LinkPolicy policy, List<String> lst, CustomTypeRepository repository)
        {


            foreach (ItemMap p in m.ItemMaps)
                if (p.Enable && p.NeedMap && !lst.Contains(p.SourceType + ":" + p.TargetType))
                {

                    

                    TypeExpression s1;
                    TypeExpression t1;
                   
                    if (p.SourceIsEnumerable || p.TargetIsArray)
                    {
                        s1 = BuildTypeExpression.Analyse(p.SourceItemType);
                        t1 = BuildTypeExpression.Analyse(p.TargetItemType);
                    }
                    else
                    {
                        s1 = BuildTypeExpression.Analyse(p.SourceType);
                        t1 = BuildTypeExpression.Analyse(p.TargetType);
                    }


                    string str1 = s1.AssemblyQualifiedName;
                    string str2 = t1.AssemblyQualifiedName;


                    if (types.Contains(str1) && types.Contains(str2))
                        continue;

                    if (!lst.Contains(str1 + ":" + str2))
                        CreateMapper(model, str1, str2, recursif, policy, lst, repository);


                }
            
        }



        private static TypeElement GetElementTypeFor(MappingModel model, string type, out bool created)
        {
            created = false;
            TypeElement t = model.FindElementFor(type);
            if (t == null)
            {
                created = true;
                t = new TypeElement(model.Partition);
                model.Elements.Add(t);
            }
            return t;
        }


        private static void SetName(MappingModel model, Mapper m)
        {
            int cnt = 1;
            string name;

            while (TestName(model, cnt, m, out name))
                cnt++;
            m.Name = name;
        }

        private static bool TestName(MappingModel model, int cnt, Mapper mapper, out string name)
        {

            name = string.Empty;
            bool t = false;
            string nam = string.Format(mapperName, mapper.Name, string.Empty);


            if (cnt == 1)
                foreach (Mapper m in model.Mappers)
                    if (m.Name == nam)
                    {
                        t = true;
                        break;
                    }


            if (t)
            {
                nam = string.Format(mapperName, mapper.Name, cnt.ToString());
                foreach (Mapper m in model.Mappers)
                    if (m.Name == nam)
                        return true;
            }

            name = nam;
            return false;
        }

    }
    
}
